package com.hwl.bigfileupload.service.impl;

import cn.hutool.core.io.FileUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hwl.bigfileupload.config.properties.UFOPProperties;
import com.hwl.bigfileupload.dto.FileChunkDTO;
import com.hwl.bigfileupload.entity.UploadTask;
import com.hwl.bigfileupload.entity.UploadTaskDetail;
import com.hwl.bigfileupload.entity.UserFile;
import com.hwl.bigfileupload.mapper.UploadTaskDetailMapper;
import com.hwl.bigfileupload.mapper.UploadTaskMapper;
import com.hwl.bigfileupload.service.UploadTaskService;
import com.hwl.bigfileupload.service.UserFileService;
import com.hwl.bigfileupload.strategy.LocalStorageUploader;
import com.hwl.bigfileupload.util.BulkFileUtil;
import com.hwl.bigfileupload.vo.CheckResultVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 上传任务表 服务实现类
 * </p>
 *
 * @author sentry
 * @since 2023-09-23
 */
@Slf4j
@Service
public class UploadTaskServiceImpl extends ServiceImpl<UploadTaskMapper, UploadTask> implements UploadTaskService {

    @Resource
    private UFOPProperties ufopProperties;
    @Resource
    private UploadTaskDetailMapper uploadTaskDetailMapper;
    @Resource
    private UserFileService userFileService;
    @Resource
    private LocalStorageUploader localStorageUploader;

    /*
     * 检查md5，如果不存在需要写入任务表，上传状态默认失败
     * 当分片上传时则写入详情表（文件不需要分片时则默认分一片），每次分片上传都要写入详情表一次
     * 如果上传成功需要修改任务表 upload_status 和 success_time 字段吗，并写入文件表
     */
    @Override
    public CheckResultVO checkUpload(FileChunkDTO dto) {
        CheckResultVO resultVO = new CheckResultVO();

        UploadTask uploadTask = baseMapper.selectOne(Wrappers.<UploadTask>lambdaQuery()
                .eq(UploadTask::getIdentifier, dto.getIdentifier())
                .orderByDesc(UploadTask::getUploadTime)
                .last("limit 1"));
        // 说明第一次上传
        if (uploadTask == null) {
            resultVO.setUploaded(false);
            // 写入任务表
            baseMapper.insert(dto.createUploadTaskEntity().setFilePath(ufopProperties.getFilePath(dto)));
            return resultVO;
        }

        if (uploadTask.getUploadStatus() == 1) {
            resultVO.setUploaded(true);
            resultVO.setUrl(userFileService.getFileUrl(dto));
            return resultVO;
        }

        /*
         * 到了这里说明文件是有过上传记录的，查找上传过的分片
         *
         * 三种方案实现获取已经上传过的分片：
         * 1、表数据中查找
         * 2、redis中获取已经上传的分片集合
         * 3、读取 .conf 文件内容
         */
        List<UploadTaskDetail> uploadTaskDetails = uploadTaskDetailMapper.selectList(Wrappers.<UploadTaskDetail>lambdaQuery()
                .eq(UploadTaskDetail::getIdentifier, dto.getIdentifier())
                .orderByAsc(UploadTaskDetail::getChunkNumber));
        if (uploadTaskDetails.isEmpty()) {
            resultVO.setUploaded(false);
            return resultVO;
        }

        // 返回已经上传过的分片集合
        resultVO.setUploadedChunks(uploadTaskDetails.stream().map(UploadTaskDetail::getChunkNumber).collect(Collectors.toList()));
        return resultVO;
    }

    @Override
    public boolean uploadFile(FileChunkDTO dto) {
        // 注：其他校验省略了
        if (dto.getFile() == null) {
            throw new RuntimeException("文件不能为空");
        }

        // 创建保存目录
        File localPath = new File(ufopProperties.getSavePath(dto.getIdentifier()));
        FileUtil.mkdir(localPath);

        File tmpFile = localStorageUploader.createTmpFile(dto.getFilename(), ufopProperties.getSavePath(dto.getIdentifier()));
        boolean uploadFlag;
        // 如果是单文件上传
        if (dto.getTotalChunks() == 1) {
            uploadFlag = this.uploadSingleFile(dto);
            dto.setUploadComplete(true);
        } else {
            // 分片上传
            uploadFlag = localStorageUploader.sliceUpload(dto, ufopProperties, tmpFile);
        }
        // 如果本次上传成功则存储数据到表中
        if (uploadFlag) {
            // 写入详情表
            uploadTaskDetailMapper.insert(dto.createTaskDetailEntity().setFilePath(ufopProperties.getFilePath(dto)));

            // 如果文件已经全部上传完成
            if (dto.getUploadComplete()) {
                // 只有切片操作才需要这样处理
                if (dto.getTotalChunks() != 1) {
                    // 检查md5是否一致
                    if (!localStorageUploader.checkFileMd5(tmpFile, dto.getIdentifier())) {
                        localStorageUploader.cleanUp(tmpFile);
                        // 不一致则删除文件记录表
                        baseMapper.delete(Wrappers.<UploadTask>lambdaQuery().eq(UploadTask::getIdentifier, dto.getIdentifier()));
                        uploadTaskDetailMapper.delete(Wrappers.<UploadTaskDetail>lambdaQuery().eq(UploadTaskDetail::getIdentifier, dto.getIdentifier()));
                        throw new RuntimeException("文件已损坏！");
                    }

                    // 重命名文件
                    localStorageUploader.renameFile(tmpFile, dto.getFilename());
                }

                baseMapper.update(null, Wrappers.<UploadTask>lambdaUpdate()
                        .set(UploadTask::getSuccessTime, LocalDateTime.now())
                        .set(UploadTask::getUploadStatus, 1)
                        .eq(UploadTask::getIdentifier, dto.getIdentifier()));
                userFileService.save(dto.createUserFileEntity().setFilePath(ufopProperties.getFilePath(dto))
                        .setFileStatus(1).setFileUrl("").setIdentifier(dto.getIdentifier()).setStorageType(0));
            }
        }
        return uploadFlag;
    }

    @Override
    public void downloadByIdentifier(String identifier, HttpServletRequest request, HttpServletResponse response) {
        UserFile userFile = userFileService.getOne(Wrappers.<UserFile>lambdaQuery()
                .eq(UserFile::getIdentifier, identifier)
                .eq(UserFile::getDeleteFlag, 0)
                .last("limit 1"));
        if (userFile == null) throw new RuntimeException("文件不存在");
        try {
            BulkFileUtil.downloadFile(request, response, new File(userFile.getFilePath()));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    private boolean uploadSingleFile(FileChunkDTO dto) {
        try {
            dto.getFile().transferTo(new File(ufopProperties.getFilePath(dto)));
            return true;
        } catch (IOException e) {
            log.error("文件上传失败：" + e);
            return false;
        }
    }
}
